home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 007 / bixdos.arc / SERCOMM < prev    next >
Encoding:
Text File  |  1986-11-24  |  16.4 KB  |  358 lines

  1. IBM PC Asynchronous Serial-Port Interrupt Programming
  2.  
  3. Text from \Info-IBMPC Digest/, volume 4, number 97
  4. (dated 21 August 1985)
  5.  
  6.  
  7. -----------------
  8.  
  9. Date: Wednesday, 21 Aug 1985 16:37:04-PDT
  10. From: mitton@beorn.dec.com (David Mitton)
  11. Subject: Interrupt Driver Async I/O
  12.  
  13.  
  14. Everything You wanted to know about PC Async Comm, but were afraid to ask....
  15. -----------------------------------------------------------------------------
  16.  
  17. A few months back I sent in a note asking for help about how to do reliable
  18. interrupt-driven communications using the IBM PC async communication port.
  19. Now I know more than you may want to hear.  But I would like to share my
  20. knowledge with the network to help out the adventurous, and expose some of the
  21. problems that make it so difficult, so that these situations might not get
  22. designed into to future products.  Scattered though out this message are some
  23. of my own editorial comments in [square brackets].
  24.  
  25. - The UART used in the IBM PC family is the Western Digital 8250 and friends
  26. (recently the National Semiconductor 8250B, and the 14650 in the PC/AT).  This
  27. UART is single-buffered.  There is only one receive-character buffer to store a
  28. byte in while the next byte is being assembled in the shift register.  This 
  29. means that you have only ONE CHARACTER INTERVAL (on average) to process that
  30. byte  before the next one wipes it out (an overrun).
  31.  
  32. I bet you thought your AT adapter card or AST Advantage has an 8250B on it?
  33. The IBM PC/AT Technical Reference manual doesn't say what the chip is (unlike
  34. the XT) or that it is different until you look at the schematic diagram.
  35. Surprise!  Look again, it's an NS 14650.  National describes the chip on the
  36. same sheet as the 8250B.  It seems to be functionally identical, but with a
  37. faster access time.  Unfortunately, it is still not fast enough for the 80286
  38. processor.  Read IBM's ISV notes on avoiding doing back-to-back I/O references
  39. to the same chip so as to not violate chip-access speeds.
  40.  
  41.         [ Modern UARTs should have at least a 4-character FIFO buffer.
  42.          This makes for fewer interrupt-latency problems and more reliability.]
  43.         [ Also better baud-rate generators and dividers are available 
  44.          these days.  19.2-kbps should be easily and accurately supported.]
  45.  
  46. - Next I did the following rough instruction budget.  An IBM PC/XT is an
  47. 8088 running at 4.77 MHz,
  48.  
  49.                          1 clock cycle =  210ns
  50.    1 memory reference = 4 clock cycles =  840ns
  51. 1 average instruction = 4 memory refs  = 3360ns 
  52.  
  53. 1 second / ( 3360ns / avg ins)  ~= 297,619 average instructions per second
  54.  
  55.  9600 bits per second = 960 characters per second
  56.  
  57. (297,619 avg ins/sec) / 960 cps  =  310 average instructions per character
  58.  
  59. Now that's only for half duplex!  Halve that for full-duplex load.
  60. And don't forgot to subtract for the time PC memory refresh uses.
  61. It becomes rather clear that the interrupt-service code path must be as short
  62. as possible, at least less than 150 instructions average.
  63.  
  64. Now you may want to quibble with my assumption of 4 clocks/average instruction.
  65. Truth be known, I made it up.  Everything I see indicates that on an 8088
  66. the average is  more   than that, and knowing that makes this estimate seem
  67. optimistic.
  68.  
  69. On the AT, things get much better because the 16-bit bus causes more to happen
  70. in fewer cycles.
  71.  
  72.   [ Now do you understand why a FIFO is needed?]
  73.   [ My code on a DEC Rainbow (which has a 3-character FIFO UART) was written in
  74.     C (including interrupt service) and worked fine after first debugged it.]
  75.  
  76. - Now if this isn't bad enough, let's toss in an interrupt-handling problem:
  77. The IBM PC 8259 Interrupt Controller is programmed in the BIOS to be edge-
  78. sensitive.  The 8250 seems to supply edges properly, except when there may
  79. be multiple interrupts to service (i.e., a full-duplex receive-complete and
  80. transmit-complete at the same time).  Now this behavior is not documented
  81. on any spec sheet I've seen, (usually because they don't tell you what happens
  82. in this case)  but rumor has it that National changed the 8250B
  83. (and the 14650) so that it does not toggle the interrupt line when presenting 
  84. such stacked interrupts.  It is unknown whether the Western Digital 8250 does
  85. the same.  But the existence of said crock has been experimentally verified
  86. many times.  It is discussed thoroughly, but sadly inconclusively, in the file
  87. EDGES.INT.
  88.  
  89.   [ Could someone get the UART people to "fess up" in writing?]
  90.   [ Also, tell them not to do it again!]
  91.  
  92. - Okay, so what do you do?  Well, you have to write your interrupt-service
  93. routines as a loop, servicing the UART until all pending interrupts have
  94. been handled and you won't lose an interrupt edge.  The COMPKG2 and the 
  95. MIT PC/IP service routines do a good job, but they have some flaws.
  96.  
  97.   1) They recheck the UART status by reading the LSR at the bottom of the loop.
  98. Since reading the LSR register will reset any pending receive-error conditions,
  99. you could easily lose notification of an overrun, framing, or parity error.
  100. It is much better to re-read the ISR instead, because it serializes the 
  101. the highest priority current status.
  102.  
  103.   2) You should EOI the 8259 interrupt controller at the beginning or in
  104. the middle of the service loop.  Notice that the 8250 clears the interrupt 
  105. condition upon servicing (reading or writing) the appropriate register.
  106. If you EOI afterwards, then there is a window in which an interrupt may
  107. arise from the UART but get dismissed when you clear the interrupt controller.
  108.  
  109.   Another bug to avoid, which I made once myself, is: do not break the loop 
  110. in the character processing.  The routine will hang with a unserviced interrupt
  111. pending on the UART and no more edges to trigger the 8259.  (unless you 
  112. implement the timer described below)
  113.  
  114. The proper loop as I coded it is as follows:
  115.  
  116.         send EOI to 8259
  117. loop:
  118.         read IIR
  119.         switch(IIR)
  120.         {
  121.                 case NO_INTERRUPT:
  122.                         iret;
  123.                 case XMIT_READY:
  124.                         send next char;
  125.                         break;
  126.                 case RECV_READY:
  127.                         receive and buffer char;
  128.                         break;
  129.                 case LINE_STATUS;
  130.                         record error condition;
  131.                         break;
  132.                 case MODEM_STATUS:
  133.                         record state change;
  134.                         break;
  135.         }
  136.         goto loop
  137.                         
  138. Note:
  139.         - EOI done outside the loop may generate extra NOP interrupts,
  140. if no stacked interrupt, but one arrives during a long service path.
  141. An inside-the-loop EOI eliminates this but adds more code to the loop.
  142.  
  143.         
  144. - Great, so far so good.  What could screw us up?  Well, I forgot to mention
  145. that what I was writing was a device driver and it runs in the background
  146. on the async ports.  Because we are not dealing with a big system with
  147. device-allocation concepts, there are all sorts of PC-DOS programs and 
  148. utilities that can stomp on your serial comm port.  The MODE command will
  149. do you in, especially if you forget to take the command out of your AUTOEXEC
  150. file [took me a month to figure that one out].  BASICA grabs the comm port.   
  151. Even Symphony thinks that it can grab the comm port for its terminal emulator,
  152. unless you do Lotus's not-well-documented re-configuration procedure.
  153.  
  154.     [software writers: please don't assume that the UART is available!]
  155.  
  156. - Another program that caused us to lose was Rosesoft's Prokey.  It had hooked
  157. on to Interrupt 1C, the user clock-tick handler, which we were using too, and
  158. spent  soooo  much time on it that our timing just totally screwed up.  This
  159. was finally solved when we fixed another problem below.
  160.  
  161.   [Be careful of doing to much on the clock tick.  It could screw up someone
  162.    else.]
  163.         
  164. - One thing that I did to add some robustness to all this (and find some bugs)
  165. was to add a timer scheme.  Essentially, whenever I started a transmission
  166. or reception of a message, I initialized a word to a nonzero timeout value.
  167. A clock-tick routine decremented the cell, if nonzero, and if it went to
  168. zero, reset it and faked an interrupt to the service routine.  This feature
  169. allowed the code to recover (as opposed to hanging forever) from lost 
  170. interrupts and errant MODE commands.  Hopefully, this should never happen.
  171. But it did at first, and a trace of the current state helped.
  172.  
  173.  [Real disasters give you first-hand experience on how to defensively program.]
  174.  
  175. - Now the device service looks good, but I am still getting overruns at 7200
  176. and 9600 bps.  So I thought some more about where the time goes in the CPU.
  177. Another way that you lose CPU instructions is to other interrupt-service
  178. routines and code sections that disable interrupts.  Unfortunately,
  179. unlike a VAX, we don't have the multiple IPL levels to synchronize CPU
  180. threads without shutting out device service.  
  181.  
  182.    In my driver there are 3 levels of synchronization that use interrupt
  183. locking around cross-level queue operations.  Unfortunately, the interrupt-
  184. locking queue function was the default even for queue operations in the 
  185. same level.  A better analysis of interrupt locking and necessary
  186. synchronization led to fewer and shorter interrupt-disabled code sections
  187. and a much better performance level.  I have now even figured out a better
  188. semaphore interlock with the interrupt-service routine that will eliminate
  189. even more interrupt-disabled code.  
  190.  
  191.         [Interrupt latency on the Intel 8086 architecture is precious!
  192.          You must try to minimize all interrupt-disabled code paths.]
  193.         [Another reason to have a FIFO in the UART!]
  194.  
  195. - Finally, I had done almost all I could think of:   I had tweaked the
  196. interrupt-service loop, bummed the code paths to a minimum, and was still
  197. getting overruns on the XT.  I still had this feeling that they might be
  198. systematic, so I put a little code in the overrun routine that recorded
  199. the segment and offset of the code interrupted just before the overrun
  200. was serviced.  A higher-level monitor printed it out.  I was perplexed
  201. because the address was always the same: FE00:FEEA.  I had sort of expected
  202. to find some code that had just done a STI, but instead I was staring at the
  203. stack-cleanup code for the clock-tick service in the ROM BIOS. (below)
  204.  
  205. FEA5    TIMER_INT PROC FAR
  206. FEA5    FB      STI             ;Interrupts back on
  207. .... push regs, increment DOS time in RAM, turn off floppies...
  208. FEE3    CD1C    INT 1CH         ;Transfer control to a user routine
  209. FEE5    B020    MOV AL,EOI
  210. FEE7    E620    OUT 020H,AL     ;End of interrupt to 8259
  211. FEE9    5A      POP DX
  212. FEEA    58      POP AX
  213. FEEB    1F      POP DS          ;Restore machine state
  214. FEEC    CF      IRET
  215.  
  216.    Walking back up the code, I don't see anything unusual.  Wait a minute!
  217. Why are we finally EOI'ing the 8259 after the INT 1C, when we STI'ed back
  218. at entry?  Oh, that's to make sure that we don't reenter, huh?  Well what
  219. about the 8259 all the time that the INT 1C handlers were running?
  220.  
  221.    Yes folks, The Single Serializing Priority Interrupt Controller has been
  222. blocked the entire time.  This was preventing any other device-interrupt
  223. service during the clock-tick handling.  Initially, the 1C handler is
  224. an IRET, and it's OK.  But in practice, my driver and other things were 
  225. on there with a substantial total code path, almost guaranteeing lossage.
  226.  
  227.    My fix to this BIOS crock had to replace the entire Interrupt-08
  228. routine, since it dispatches the interrupt 1C and has that EOI in the
  229. end. I wrote the following code in MWC (Mark Williams C) assembler
  230. that handled problem of reentrancy  as well:
  231.  
  232. / Interrupt 08 Handler
  233. / This routine *REPLACES* the IBM PC BIOS interrupt handler for the
  234. / clock-frequency interrupt.  It *MUST* be loaded into the system before
  235. / any other INT 08h user.
  236. / We must replace the BIOS routine because it has a nasty bug in that
  237. / it does not reset the 8259 Interrupt Controller until the INT 1Ch Handler(s)
  238. / are done.  This effectively locks interrupts for the entire period.
  239. / Chaining to the original handler would not work.
  240. /
  241. TIMER_LOW       = 0x006C
  242. TIMER_HIGH      = 0x006E
  243. TIMER_OFL       = 0x0070
  244. MOTOR_STATUS    = 0x003F
  245. MOTOR_COUNT     = 0x0040
  246.  
  247.         .shri
  248. ticks:  .word   0               / ticks flag
  249.  
  250.         .globl  cex_clock_
  251. cex_clock_:                     / Timer interrupt entry point
  252.         push    ds
  253.         push    ax
  254.         push    dx
  255.         mov     ax, $0x40       / set BIOS DATA segment value
  256.         mov     ds, ax
  257.  
  258.         inc     TIMER_LOW       / Increment BIOS Time of day
  259.         jnz     T4
  260.         inc     TIMER_HIGH
  261. T4:
  262.         cmp     TIMER_HIGH, $0x18
  263.         jnz     T5
  264.         cmp     TIMER_LOW, $0xB0
  265.         jnz     T5
  266.  
  267.         sub     ax, ax
  268.         mov     TIMER_HIGH, ax
  269.         mov     TIMER_LOW, ax
  270.         movb    TIMER_OFL, $1
  271. T5:                             / Test for diskette timeout
  272.         decb    MOTOR_COUNT
  273.         jnz     T6
  274.         andb    MOTOR_STATUS, $0xF0
  275.         movb    al, $0x0C
  276.         mov     dx, $0x03F2
  277.         outb    dx, al
  278. T6:                             /New code starts here----
  279.         movb    al, $0x20               / reset 8259 Interrupt Controller
  280.         outb    0x20, al                / this allows other interrupts to queue
  281.         inc     cs:ticks                / increment tick semphore
  282.         cmp     cs:ticks, $1            /is it the first time?
  283.         jne     T8                      /No, just leave
  284.  
  285. T7:     pushf                           /fake interrupt call to local routine
  286.         cli                             /
  287.         push    cs                      /
  288.         call    cex_clicker             / giving us first priority
  289.  
  290.         int     0x1c                    / call everyone else
  291.  
  292.         cli                             / disable interrupts, if reenabled
  293.         dec     cs:ticks                / check if others came in
  294.         jnz     T7                      / yes, do it again
  295.  
  296. T8:     pop     dx                      / restore state
  297.         pop     ax
  298.         pop     ds
  299.         iret                            / exit interrupt service
  300.  
  301.  
  302. This routine also solved the Prokey problem mentioned above, since
  303. I hooked my driver directly in the clock-tick chain before anyone else.
  304. The 4 instructions after T7 can be removed, and this can be used as a
  305. general-purpose INT-08-handler replacement.   After installing this,
  306. I noticed that my Polygon terminal emulator no longer gets overruns either.
  307.  
  308. There may be some 1C clock-tick users that would be upset by the change,
  309. but I haven't found one yet.  (Even the PC Network tolerates it)
  310. Another way to work around this problem, if you don't need priority over
  311. the 1C handlers, is to hook INT 08, call it and wait for it to return.
  312. There are probably yet another way around this problem, but this seems the 
  313. cleanest to me.
  314.  
  315.         [DOS needs better clock tick services!]
  316.         [and don't hog them!]
  317.  
  318. Dave Mitton, DECnet-DOS Development.
  319. /*opinions expressed here are mine, although Digital Equipment Corporation
  320.  might feel likewise*/
  321. Enet: OLORIN::MITTON    
  322. arpanet: mitton%olorin.dec@decwrl
  323. usenet: decwrl!dec-rhea!dec-olorin!mitton
  324.  
  325. Posted: Wed 21-Aug-1985 19:33 Eastern Standard Time, Tewksbury, Mass.
  326. To:     RHEA::DECWRL::"info-ibmpc@usc-isib "
  327.  
  328. /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
  329. Postscript:
  330. \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
  331.  
  332. Date: 18-Aug-1986 1326
  333. From: ihnp4!decwrl!bergil.dec.com!mitton  (Dave Mitton)
  334. To:   "Richard S. Shuford"
  335. Subject: Serial line info permission
  336.  
  337. >  I would like, with your permission, to re-post the material
  338. >  in a conference on BIX, the BYTE Information Exchange.
  339.  
  340. Sure.  I'd like to add as an addendum that I have seen some really creative
  341. ways to work around the EOI-after-INT-1C problem since posting that article.
  342.  
  343. The cleverest is:
  344.         - trust the stack layout not to change
  345.         - do the EOI to enable interrupts
  346.         - do your thing
  347.         - instead of returning to the BIOS handler, do the IRET yourself.
  348.  
  349. If you trust the INT 1C handlers to not take too much time:
  350.         - Chain onto INT 08
  351.         - take no action on the initial interrupt, 
  352.                 but fake an Interrupt call to your predecessor
  353.         - so that the IRET returns to you, after the EOI has been done
  354.         - then do your thing
  355.         - IRET
  356.  
  357.        Dave.
  358.